package com.yuzhihao.writejvm.attributes;

import com.yuzhihao.writejvm.clazz.ClassInfo;
import com.yuzhihao.writejvm.constant.ConstantUtf8Info;
import com.yuzhihao.writejvm.format.PrintFormat;
import com.yuzhihao.writejvm.read.Parse;
import com.yuzhihao.writejvm.read.TypeFatory;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.log4j.Log4j2;
import org.springframework.util.CollectionUtils;

import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * The MethodParameters attribute is a variable-length attribute in the attributes table of a method_info structure (§4.6). A MethodParameters attribute records information about the formal parameters of a method, such as their names.
 * <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24">MethodParametersAttribute</a>
 *
 * 方法的参数属性 是方法下面的第一级别属性
 * @author yzh
 * @since 2021年12月11日18:23:40
 */
@Data
@Log4j2
@EqualsAndHashCode(callSuper = true)
public class MethodParametersAttribute extends Attributes {

    private int parameters_count_u1 = 1;
    private int parameters_count;

    private List<Parameters> parameters = new ArrayList<>();

    public MethodParametersAttribute(String name){
        super();
        super.setAttribute_name(name);
    }

    @Override
    public void parse(DataInputStream dis, ClassInfo ci) {
        try {
            super.attribute_length = Parse.byteToInt(super.attribute_length_u4, dis);
            this.parameters_count = Parse.byteToInt(this.parameters_count_u1, dis);

            int i = 0;
            while (i < this.parameters_count){
                Parameters parameter = new Parameters();
                parameter.parse(dis, ci);
                this.parameters.add(parameter);
                i++;
            }
        } catch (IOException e) {
            log.error(e);
        }
    }

    @Override
    public String print(String print) {
        StringBuilder builder = new StringBuilder();
        builder.append(print).append("MethodParameters").append(PrintFormat.COLON).append(System.lineSeparator());

        String name = "Name           ";
        String flags = "Flags           ";
        builder.append(print).append(PrintFormat.EMPTY).append(name).append(flags).append(System.lineSeparator());
        for (Parameters parameter : this.parameters) {
            builder.append(print).append(PrintFormat.EMPTY);
            builder.append(stable(parameter.getConstant(),name));
            List<TypeFatory.Access> accesses = TypeFatory.access_flags(parameter.access_flags);
            StringBuilder flag = new StringBuilder();
            if(!CollectionUtils.isEmpty(accesses)){
                for (TypeFatory.Access access : accesses) {
                    flag.append(access.getFname()).append(",");
                }
                flag.delete(flag.length()-1,flag.length());
            }
            builder.append(stable(flag.toString(),flags));
            builder.append(System.lineSeparator());
        }
        return builder.toString();
    }

    @Data
    @Log4j2
    public static class Parameters implements Parse{
        private int name_index_u2 = 2;
        private int name_index;

        private int access_flags_u2 = 2;
        private int access_flags;

        private String constant;

        private ClassInfo ci;

        public String getConstant() {
            if(Objects.nonNull(this.constant)){
                return this.constant;
            }

            ConstantUtf8Info cp = (ConstantUtf8Info)ci.getCp_info().getPools().get(String.valueOf(this.name_index));

            this.constant = cp.getConstant();

            return constant;
        }

        @Override
        public void parse(DataInputStream dis, ClassInfo ci) {
            try {
                this.name_index = Parse.byteToInt(this.name_index_u2, dis);
                this.access_flags = Parse.byteToInt(this.access_flags_u2, dis);
                this.ci = ci;
            } catch (IOException e) {
                log.error(e);
            }
        }
    }
}
